#pragma once

#include "../Bim_Ifc_Data.h"
#include "GeomNode.h"
#include <iomanip> 
#include <fstream>

namespace ComponentStructure
{
	using std::vector;
	using std::ifstream;
	using std::ofstream;
	using std::string;
	using GeomStructure::Point3D;
	using GeomStructure::Vector3d;

#define COMPONENT_GROUP_START_TAG "ALLCOMPONENTSTART"
#define COMPONENT_GROUP_END_TAG "ALLCOMPONENTFINISH "
#define COMPONENT_START_TAG "COMPONENT"
#define COMPONENT_END_TAG "COMPONENTFINISH"
#define MESH_START_TAG "Mesh"
#define MESH_END_TAG "Mesh"
#define VERTEX_START_TAG "Vertex"
#define TRIANGLE_START_TAG "Triangle"
#define NORMAL_START_TAG "Normal"



	template<class T>
	class Component;

	template <class T>
	class ComponentGroup;

	template <class T>
	class Mesh
	{
	public:
		friend class Component<T>;
	public:
		Mesh<T> operator = (Mesh<T>&&);
	private:
		int _id;
		vector<Point3D<T>> _vertex;
		vector<Vector3d> _normal;
		vector<int> _triangle;

	private:
		friend ifstream& operator >> <T>(ifstream& in, Mesh<T>& obj);
		friend ofstream& operator << <T>(ofstream& out, const Mesh<T>& obj);
	};

	template<class T>
	Mesh<T> Mesh<T>::operator = (Mesh<T>&& mesh)
	{
		_vertex = std::move(mesh._vertex);
		_normal = std::move(mesh._normal);
		_triangle = std::move(mesh._triangle);
		return *this;
	}

	template<class T>
	ifstream& operator >> (ifstream& in, Mesh<T>& obj)
	{
		if (!in)
			return in;

		string strTemp;

		in >> strTemp;
		in >> strTemp;
		if (strcmp(strTemp.c_str(), MESH_START_TAG) != 0)
			return in;

		in >> obj._id;
		in >> strTemp >> strTemp >> strTemp;

		if (strcmp(strTemp.c_str(), VERTEX_START_TAG) != 0)
			return in;

		size_t vNum, tNum, nNum;
		T x, y, z;
		size_t ix;
		double dx, dy, dz;
		in >> vNum;
		for (size_t i = 0; i < vNum; i++)
		{
			in >> x >> y >> z;
			obj._vertex.push_back(Point3D<T>(x, y, z));
		}

		in >> strTemp >> strTemp;

		if (strcmp(strTemp.c_str(), TRIANGLE_START_TAG) != 0)
			return in;

		in >> tNum;
		in.get();
		for (size_t i = 0; i < tNum; i++)
		{
			while (char t = in.peek())
			{
				if (t == '\n')
				{
					in.get();
					break;
				}
				in >> ix;
				obj._triangle.push_back(ix);
			}

		}

		in >> strTemp >> strTemp;

		if (strcmp(strTemp.c_str(), NORMAL_START_TAG) != 0)
			return in;

		in >> nNum;

		for (size_t i = 0; i < nNum; i++)
		{
			in >> dx >> dy >> dz;
			obj._normal.push_back(Vector3d(dx, dy, dz));
		}

		while (getline(in, strTemp) && strTemp.empty());

		return in;
	}

	template<class T>
	ofstream& operator << (ofstream& out, const Mesh<T>& obj)
	{
		if (!out)
			return out;
		out.setf(std::ios::left);
		
		out << std::setw(8) << "#" << std::setw(8)<<MESH_START_TAG << std::setw(8) << obj._id << std::setw(8) << "Begin" << endl << endl;
		out << std::setw(8) << "#" << std::setw(8) << VERTEX_START_TAG << std::setw(8) << obj._vertex.size() << endl;

		const vector<Point3D<T>>& vP3D = obj._vertex;
		for (vector<Point3D<T>>::size_type i = 0; i != vP3D.size(); i++)
		{
			out << std::setw(8) << vP3D[i].x << std::setw(8) << vP3D[i].y << std::setw(8) << vP3D[i].z << endl;
		}
		out << endl;
		out << std::setw(8) << "#" << std::setw(16) << TRIANGLE_START_TAG << std::setw(8) << obj._triangle.size() << endl;
		const vector<int>& vInt = obj._triangle;

		for (vector<int>::size_type i = 0, len = vInt.size() / 3; i < len; i++)
		{
			out << std::setw(8) << vInt[i] << std::setw(8) << vInt[i + 1] << std::setw(8) << vInt[i + 2] << endl;
		}
		out << endl;

		out << std::setw(8) << "#" << std::setw(8) << MESH_END_TAG << std::setw(8) << obj._id << std::setw(8) << "End" << endl << endl;

		return out;
	}

	template<class T>
	class Component
	{
	public:
		friend class ComponentGroup<T>;
	public:
		Component<T> operator = (Component<T>&&);

	private:
		vector<Mesh<T>> _mesh;
		int _id;
		int _group_id;
		string _type;

	private:
		friend ifstream& operator >> <T>(ifstream& in, Component<T>& obj);
		friend ofstream& operator << <T>(ofstream& out, const Component<T>& obj);
	};

	template<class T>
	Component<T> Component<T>::operator = (Component<T>&& comp)
	{
		_type = std::move(comp._type);
		_id = comp._id;
		_group_id = comp._group_id;
		_mesh = std::move(comp._mesh);
		return *this;
	}

	template<class T>
	ifstream& operator >> (ifstream& in, Component<T>& obj)
	{
		if (!in)
			return in;

		string strTemp;
		in >> strTemp;
		if (strcmp(strTemp.c_str(), COMPONENT_START_TAG) != 0)
			return in;

		in >> obj._group_id;
		in >> strTemp;
		obj._id = atoi(strtok(const_cast<char*>(strTemp.c_str()), "="));
		obj._type = std::move(string(strtok(NULL, "=")));

		Mesh<T> meshTemp;
		while (char t = in.peek())
		{
			if (t == '\n')
			{
				in.get();
				continue;
			}

			if (t == 'C')
				break;

			in >> meshTemp;
			obj._mesh.push_back(meshTemp);
		}
		getline(in, strTemp);
		return in;

	}

	template<class T>
	ofstream& operator << (ofstream& out, const Component<T>& obj)
	{
		if (!out)
			return out;
		out << COMPONENT_START_TAG << " " << obj._group_id
			<< " " << obj._id << "=" << obj._type << endl;

		for (vector<Mesh<T>>::size_type i = 0; i < obj._mesh.size(); i++)
		{
			out << obj._mesh[i];
		}


		out << endl << COMPONENT_END_TAG << endl << endl;
		return out;
	}

	template<class T>
	class ComponentGroup
	{
	private:
		vector<Component<T>> _component;
		int _id;
	public:
		ComponentGroup() {}
		ComponentGroup(ComponentGroup<T>&&);
		/*static void OutputWithoutNormal(const ComponentGroup<T>& cmpGrp, const char* fName);*/
	public:
		friend ifstream& operator >> <T>(ifstream& in, ComponentGroup<T>& obj);
		friend ofstream& operator << <T>(ofstream& out, const ComponentGroup<T>& obj);
	};

	template<class T>
	ComponentGroup<T>::ComponentGroup(ComponentGroup&& cGroup)
	{
		_id = cGroup._id;
		_component = std::move(cGroup._component);
	}

	

	template<class T>
	ifstream& operator >> (ifstream& in, ComponentGroup<T>& obj)
	{
		if (!in)
			return in;

		string strTemp;
		in >> strTemp;

		if (strcmp(strTemp.c_str(), COMPONENT_GROUP_START_TAG) != 0)
			return in;

		in >> obj._id;
		Component<T> compTemp;
		vector<Component<T>>& vComp = obj._component;
		while (char t = in.peek())
		{
			if (t == '\n')
			{
				in.get();
				continue;
			}

			if (t == 'A')
				break;

			in >> compTemp;
			vComp.push_back(compTemp);
		}
		getline(in, strTemp);
		return in;
	}

	template<class T>
	ofstream& operator << (ofstream& out, const ComponentGroup<T>& obj)
	{
		if (!out)
			return out;

		out << COMPONENT_GROUP_START_TAG << " " << obj._id << endl;
		const vector<Component<T>>& vComp = obj._component;

		for (vector<Component<T>>::size_type i = 0; i < vComp.size(); i++)
		{
			out << vComp[i];
		}
		out << COMPONENT_GROUP_END_TAG << " " << obj._id;
		return out;
	}
}

